/*-------------------------------------------------------------------------
 *
 * spgvalidate.c
 *	  Opclass validator for SP-GiST.
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			src/backend/access/spgist/spgvalidate.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/amvalidate.h"
#include "access/htup_details.h"
#include "access/spgist_private.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/regproc.h"
#include "utils/syscache.h"


/*
 * SP-GiST 操作类的验证器。
 *
 * 这里的一些检查涵盖整个操作族，因此在检查一个族中的每个操作类时是冗余的。 但是它们的运行时间不足以成为问题，因此我们接受重复性，而不是使 amvalidate API 更复杂。
 */
bool spgvalidate(Oid fc_opclassoid)
{
	bool		fc_result = true;
	HeapTuple	fc_classtup;
	Form_pg_opclass fc_classform;
	Oid			fc_opfamilyoid;
	Oid			fc_opcintype;
	Oid			fc_opckeytype;
	char	   *fc_opclassname;
	HeapTuple	fc_familytup;
	Form_pg_opfamily fc_familyform;
	char	   *fc_opfamilyname;
	CatCList   *fc_proclist,
			   *fc_oprlist;
	List	   *fc_grouplist;
	OpFamilyOpFuncGroup *fc_opclassgroup;
	int			fc_i;
	ListCell   *fc_lc;
	spgConfigIn fc_configIn;
	spgConfigOut fc_configOut;
	Oid			fc_configOutLefttype = InvalidOid;
	Oid			fc_configOutRighttype = InvalidOid;
	Oid			fc_configOutLeafType = InvalidOid;

	/* 获取操作类信息 */
	fc_classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(fc_opclassoid));
	if (!HeapTupleIsValid(fc_classtup))
		elog(ERROR, "cache lookup failed for operator class %u", fc_opclassoid);
	fc_classform = (Form_pg_opclass) GETSTRUCT(fc_classtup);

	fc_opfamilyoid = fc_classform->opcfamily;
	fc_opcintype = fc_classform->opcintype;
	fc_opckeytype = fc_classform->opckeytype;
	fc_opclassname = NameStr(fc_classform->opcname);

	/* 获取操作族信息 */
	fc_familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(fc_opfamilyoid));
	if (!HeapTupleIsValid(fc_familytup))
		elog(ERROR, "cache lookup failed for operator family %u", fc_opfamilyoid);
	fc_familyform = (Form_pg_opfamily) GETSTRUCT(fc_familytup);

	fc_opfamilyname = NameStr(fc_familyform->opfname);

	/* 获取操作族的所有操作符和支持函数 */
	fc_oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(fc_opfamilyoid));
	fc_proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(fc_opfamilyoid));
	fc_grouplist = identify_opfamily_groups(fc_oprlist, fc_proclist);

	/* 检查单个支持函数 */
	for (fc_i = 0; fc_i < fc_proclist->n_members; fc_i++)
	{
		HeapTuple	fc_proctup = &fc_proclist->members[fc_i]->tuple;
		Form_pg_amproc fc_procform = (Form_pg_amproc) GETSTRUCT(fc_proctup);
		bool		fc_ok;

		/*
		 * 所有 SP-GiST 支持函数应该与匹配的
		 * 左/右类型注册
		 */
		if (fc_procform->amproclefttype != fc_procform->amprocrighttype)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains support function %s with different left and right input types",
							fc_opfamilyname, "spgist",
							format_procedure(fc_procform->amproc))));
			fc_result = false;
		}

		/* 检查过程编号和函数签名 */
		switch (fc_procform->amprocnum)
		{
			case SPGIST_CONFIG_PROC:
				fc_ok = check_amproc_signature(fc_procform->amproc, VOIDOID, true,
											2, 2, INTERNALOID, INTERNALOID);
				fc_configIn.attType = fc_procform->amproclefttype;
				memset(&fc_configOut, 0, sizeof(fc_configOut));

				OidFunctionCall2(fc_procform->amproc,
								 PointerGetDatum(&fc_configIn),
								 PointerGetDatum(&fc_configOut));

				fc_configOutLefttype = fc_procform->amproclefttype;
				fc_configOutRighttype = fc_procform->amprocrighttype;

				/* 默认叶子类型是 opckeytype 或输入类型 */
				if (OidIsValid(fc_opckeytype))
					fc_configOutLeafType = fc_opckeytype;
				else
					fc_configOutLeafType = fc_procform->amproclefttype;

				/* 如果指定了其他叶子数据类型，则发出警告 */
				if (OidIsValid(fc_configOut.leafType) &&
					fc_configOutLeafType != fc_configOut.leafType)
				{
					ereport(INFO,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("SP-GiST leaf data type %s does not match declared type %s",
									format_type_be(fc_configOut.leafType),
									format_type_be(fc_configOutLeafType))));
					fc_result = false;
					fc_configOutLeafType = fc_configOut.leafType;
				}

				/*
				 * 当叶子和属性类型相同时，压缩
				 * 函数不是必需的，我们在
				 * functionset 中设置相应的位，以便后续组一致性检查。
				 */
				if (fc_configOutLeafType == fc_configIn.attType)
				{
					foreach(fc_lc, fc_grouplist)
					{
						OpFamilyOpFuncGroup *fc_group = lfirst(fc_lc);

						if (fc_group->lefttype == fc_procform->amproclefttype &&
							fc_group->righttype == fc_procform->amprocrighttype)
						{
							fc_group->functionset |=
								((uint64) 1) << SPGIST_COMPRESS_PROC;
							break;
						}
					}
				}
				break;
			case SPGIST_CHOOSE_PROC:
			case SPGIST_PICKSPLIT_PROC:
			case SPGIST_INNER_CONSISTENT_PROC:
				fc_ok = check_amproc_signature(fc_procform->amproc, VOIDOID, true,
											2, 2, INTERNALOID, INTERNALOID);
				break;
			case SPGIST_LEAF_CONSISTENT_PROC:
				fc_ok = check_amproc_signature(fc_procform->amproc, BOOLOID, true,
											2, 2, INTERNALOID, INTERNALOID);
				break;
			case SPGIST_COMPRESS_PROC:
				if (fc_configOutLefttype != fc_procform->amproclefttype ||
					fc_configOutRighttype != fc_procform->amprocrighttype)
					fc_ok = false;
				else
					fc_ok = check_amproc_signature(fc_procform->amproc,
												fc_configOutLeafType, true,
												1, 1, fc_procform->amproclefttype);
				break;
			case SPGIST_OPTIONS_PROC:
				fc_ok = check_amoptsproc_signature(fc_procform->amproc);
				break;
			default:
				ereport(INFO,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
								fc_opfamilyname, "spgist",
								format_procedure(fc_procform->amproc),
								fc_procform->amprocnum)));
				fc_result = false;
				continue;		/* 不想要额外的消息 */
		}

		if (!fc_ok)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
							fc_opfamilyname, "spgist",
							format_procedure(fc_procform->amproc),
							fc_procform->amprocnum)));
			fc_result = false;
		}
	}

	/* 检查单个操作符 */
	for (fc_i = 0; fc_i < fc_oprlist->n_members; fc_i++)
	{
		HeapTuple	fc_oprtup = &fc_oprlist->members[fc_i]->tuple;
		Form_pg_amop fc_oprform = (Form_pg_amop) GETSTRUCT(fc_oprtup);
		Oid			fc_op_rettype;

		/* TODO: 检查是否仅存在允许的策略编号 */
		if (fc_oprform->amopstrategy < 1 || fc_oprform->amopstrategy > 63)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
							fc_opfamilyname, "spgist",
							format_operator(fc_oprform->amopopr),
							fc_oprform->amopstrategy)));
			fc_result = false;
		}

		/* spgist 支持 ORDER BY 操作符 */
		if (fc_oprform->amoppurpose != AMOP_SEARCH)
		{
			/* ... 并且操作符结果必须与声明的 btree opfamily 匹配 */
			fc_op_rettype = get_op_rettype(fc_oprform->amopopr);
			if (!opfamily_can_sort_type(fc_oprform->amopsortfamily, fc_op_rettype))
			{
				ereport(INFO,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
								fc_opfamilyname, "spgist",
								format_operator(fc_oprform->amopopr))));
				fc_result = false;
			}
		}
		else
			fc_op_rettype = BOOLOID;

		/* 检查操作符签名 --- 所有 spgist 策略都相同 */
		if (!check_amop_signature(fc_oprform->amopopr, fc_op_rettype,
								  fc_oprform->amoplefttype,
								  fc_oprform->amoprighttype))
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
							fc_opfamilyname, "spgist",
							format_operator(fc_oprform->amopopr))));
			fc_result = false;
		}
	}

	/* 现在检查操作符/函数组的不一致性 */
	fc_opclassgroup = NULL;
	foreach(fc_lc, fc_grouplist)
	{
		OpFamilyOpFuncGroup *fc_thisgroup = (OpFamilyOpFuncGroup *) lfirst(fc_lc);

		/* 记住与测试操作类完全匹配的组 */
		if (fc_thisgroup->lefttype == fc_opcintype &&
			fc_thisgroup->righttype == fc_opcintype)
			fc_opclassgroup = fc_thisgroup;

		/*
		 * 如果存在函数但没有
		 * 操作符的任何数据类型对，进行投诉。 这是我们目前能做到的，检测缺失的操作符。
		 */
		if (fc_thisgroup->operatorset == 0)
		{
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s is missing operator(s) for types %s and %s",
							fc_opfamilyname, "spgist",
							format_type_be(fc_thisgroup->lefttype),
							format_type_be(fc_thisgroup->righttype))));
			fc_result = false;
		}

		/*
		 * 如果我们缺少任何数据类型的函数，进行投诉，记住
		 * SP-GiST 不使用跨类型支持函数。
		 */
		if (fc_thisgroup->lefttype != fc_thisgroup->righttype)
			continue;

		for (fc_i = 1; fc_i <= SPGISTNProc; fc_i++)
		{
			if ((fc_thisgroup->functionset & (((uint64) 1) << fc_i)) != 0)
				continue;		/* 明白了 */
			if (fc_i == SPGIST_OPTIONS_PROC)
				continue;		/* 可选方法 */
			ereport(INFO,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("operator family \"%s\" of access method %s is missing support function %d for type %s",
							fc_opfamilyname, "spgist", fc_i,
							format_type_be(fc_thisgroup->lefttype))));
			fc_result = false;
		}
	}

	/* 检查原名操作类是否受支持 */
	/* （如果组存在，我们已经在上面充分检查过） */
	if (!fc_opclassgroup)
	{
		ereport(INFO,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("operator class \"%s\" of access method %s is missing operator(s)",
						fc_opclassname, "spgist")));
		fc_result = false;
	}

	ReleaseCatCacheList(fc_proclist);
	ReleaseCatCacheList(fc_oprlist);
	ReleaseSysCache(fc_familytup);
	ReleaseSysCache(fc_classtup);

	return fc_result;
}

/*
 * 向 SP-GiST 操作族添加操作符/函数的预检查函数。
 */
void spgadjustmembers(Oid fc_opfamilyoid,
				 Oid fc_opclassoid,
				 List *fc_operators,
				 List *fc_functions)
{
	ListCell   *fc_lc;

	/*
	 * SP-GiST 操作族的操作成员不应具有硬
	 * 依赖关系，因为它们与操作族的连接仅取决于
	 * 支持函数的看法，而这可以改变。 为了
	 * 一致性，我们使所有软依赖指向操作族，
	 * 尽管在 CREATE OPERATOR CLASS 的情况下，指向操作类的软依赖也能工作。
	 */
	foreach(fc_lc, fc_operators)
	{
		OpFamilyMember *fc_op = (OpFamilyMember *) lfirst(fc_lc);

		fc_op->ref_is_hard = false;
		fc_op->ref_is_family = true;
		fc_op->refobjid = fc_opfamilyoid;
	}

	/*
	 * 必需的支持函数应具有硬依赖关系。 最好
	 * 这些只是对操作类的依赖，但如果我们在 ALTER
	 * OPERATOR FAMILY 中，我们将依赖关系保持指向整个
	 * 操作族。 （考虑到 SP-GiST 操作类通常不共享
	 * 操作族，因此似乎没有必要更加努力。）
	 */
	foreach(fc_lc, fc_functions)
	{
		OpFamilyMember *fc_op = (OpFamilyMember *) lfirst(fc_lc);

		switch (fc_op->number)
		{
			case SPGIST_CONFIG_PROC:
			case SPGIST_CHOOSE_PROC:
			case SPGIST_PICKSPLIT_PROC:
			case SPGIST_INNER_CONSISTENT_PROC:
			case SPGIST_LEAF_CONSISTENT_PROC:
				/* 必需的支持函数 */
				fc_op->ref_is_hard = true;
				break;
			case SPGIST_COMPRESS_PROC:
			case SPGIST_OPTIONS_PROC:
				/* 可选，因此强制其成为软家族依赖 */
				fc_op->ref_is_hard = false;
				fc_op->ref_is_family = true;
				fc_op->refobjid = fc_opfamilyoid;
				break;
			default:
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
						 errmsg("support function number %d is invalid for access method %s",
								fc_op->number, "spgist")));
				break;
		}
	}
}
